package com.sayice.robotconflict.game.object;

import com.sayice.robotconflict.entity.RobotEntity;
import com.sayice.robotconflict.game.Game;
import com.sayice.robotconflict.game.object.logic.Logic;
import com.sayice.robotconflict.game.object.property.*;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.function.Supplier;

/**
 * 机器人
 *
 * @author shubing
 */
@Slf4j
public class Robot {
    /**
     * 攻击偏向-塔
     */
    private static final Integer ATTACK_BIAS_TOWER = 1;
    
    /**
     * 攻击偏向-机器人
     */
    private static final Integer ATTACK_BIAS_ROBOT = 2;
    
    /**
     * 寻路网格大小
     */
    private static final int GRID_SIZE = 100;
    
    /**
     * 机器人名
     */
    public final String name;
    
    /**
     * 逻辑
     */
    private List<Logic> logics;
    
    // 上面的属性初始化自数据库
    
    /**
     * 所属玩家
     */
    public final Player player;
    
    /**
     * 在玩家携带机器人中的序号 (从0开始)
     */
    public final int seq;
    
    /**
     * 是否被控制
     */
    public volatile boolean controlled;
    
    /**
     * 血量
     */
    public int blood = Game.ROBOT_MAX_BLOOD;
    
    /**
     * 坐标
     */
    public final RewindableCoord coord = new RewindableVolatileCoord(0, 0);
    
    /**
     * 方向
     */
    public final Direction direction = new Direction(0, -1);
    
    /**
     * 移动速度，即一个 Tick 的移动距离
     */
    public double speed;
    
    /**
     * 下一个速度
     */
    public volatile double nextSpeed;
    
    /**
     * 攻击值
     */
    public volatile int attackValue = 100;
    
    /**
     * 攻击偏向
     */
    public volatile Integer attackBias;
    
    /**
     * 攻击偏移
     */
    public final Coord attackOffset = new SimpleCoord(0.0, 0.0);
    
    /**
     * 攻击目标
     */
    private volatile Object attackTarget;
    
    /**
     * 攻击冻结时间 (单位：tick)
     */
    private int attackDisabledTime;
    
    /* ---------------- 寻路相关属性 -------------- */
    
    /**
     * 移动目标位置获取函数 (寻路)
     */
    public Supplier<Coord> moveTargetSupplier;
    
    /**
     * 移动目标距离 (当机器人与目标位置的距离小于等于此值时便认为已经到达终点)
     */
    public int moveTargetDistance;
    
    /**
     * 已访问节点 (寻路)
     */
    private final Map<Node, Node> visitedNodes = new HashMap<>(16);
    
    /**
     * 开放列表 (寻路)
     */
    private final PriorityQueue<Node> openList = new PriorityQueue<>();
    
    /**
     * 发生碰撞的机器人 (寻路)
     */
    private Robot collidedRobot;
    
    /**
     * 不可达节点剩余有效路程 (寻路)
     */
    private int unreachableValidDistance;
    
    /**
     * 下一个节点 (寻路)
     */
    private Node nextNode;
    
    /**
     * 寻路节点
     */
    private static class Node implements Comparable<Node> {
        /**
         * x坐标
         */
        private final double x;
        
        /**
         * y坐标
         */
        private final double y;
        
        /**
         * G值
         */
        private int g;
        
        /**
         * H值
         */
        private int h;
        
        /**
         * 父节点
         */
        private Node parent;
        
        /**
         * 状态 (true-开放，false-关闭)
         */
        private Boolean state;
        
        private Node(double x, double y, Node parent) {
            this.x = x;
            this.y = y;
            this.parent = parent;
        }
        
        /**
         * 设置状态为开放
         *
         * @return 当前节点
         */
        private Node open() {
            state = true;
            return this;
        }
        
        /**
         * 设置状态为关闭
         *
         * @return 当前节点
         */
        private Node close() {
            state = false;
            return this;
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(Math.round(x), Math.round(y));
        }
        
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof Node)) {
                return false;
            }
            final double diff = 1e-6;
            Node node = (Node) o;
            return Math.abs(x - node.x) < diff && Math.abs(y - node.y) < diff;
        }
        
        @Override
        public int compareTo(Node o) {
            return (g + h) - (o.g + o.h);
        }
    }
    
    public Robot(RobotEntity robotEntity, Player player, int seq) {
        this.name = robotEntity.getName();
        this.logics = robotEntity.getLogics();
        this.player = player;
        this.seq = seq;
    }
    
    /**
     * 对局后重置
     */
    public void reset() {
        blood = Game.ROBOT_MAX_BLOOD;
        nextSpeed = 0;
        attackValue = 100;
        attackBias = null;
        attackTarget = null;
        moveTargetSupplier = null;
        collidedRobot = null;
        unreachableValidDistance = 0;
        nextNode = null;
    }
    
    /**
     * 是否与矩形盒体发生碰撞
     *
     * @param x1 矩形左上角 x 坐标
     * @param y1 矩形左上角 y 坐标
     * @param x2 矩形右下角 x 坐标
     * @param y2 矩形右下角 y 坐标
     * @return true表示发送碰撞，false表示未发生碰撞
     */
    public boolean collidedWith(double x1, double y1, double x2, double y2) {
        return Game.collided(coord.getX(), coord.getY(), coord.getX() + Game.ROBOT_SIZE, coord.getY() + Game.ROBOT_SIZE, x1, y1, x2, y2);
    }
    
    /**
     * 攻击
     */
    public void attack() {
        if (player.team == null) {
            return;
        }
        Team rivalTeam = player.team.room.rivalOf(player.team);
        if (ATTACK_BIAS_TOWER.equals(attackBias)) {
            if (coord.sqrDistanceTo(rivalTeam.towerCoord) <= Game.SQR_ROBOT_ATTACK_RADIUS) {
                attackTarget = rivalTeam;
            }
        } else {
            // 找到距离最近的存活的敌方机器人
            Robot target = null;
            double minSqrRobotDistance = Double.MAX_VALUE;
            for (Robot rivalRobot : rivalTeam.robots) {
                double sqrDistance = coord.sqrDistanceTo(rivalRobot.coord);
                if (sqrDistance < minSqrRobotDistance) {
                    target = rivalRobot;
                    minSqrRobotDistance = sqrDistance;
                }
            }
            if (ATTACK_BIAS_ROBOT.equals(attackBias)) {
                if (minSqrRobotDistance <= Game.SQR_ROBOT_ATTACK_RADIUS) {
                    attackTarget = target;
                }
            } else {
                double sqrTowerDistance = coord.sqrDistanceTo(rivalTeam.towerCoord);
                if (minSqrRobotDistance < sqrTowerDistance) {
                    if (minSqrRobotDistance <= Game.SQR_ROBOT_ATTACK_RADIUS) {
                        attackTarget = target;
                    }
                } else {
                    if (sqrTowerDistance <= Game.SQR_ROBOT_ATTACK_RADIUS) {
                        attackTarget = rivalTeam;
                    }
                }
            }
        }
    }
    
    /**
     * 寻路，设置方向
     */
    public void findRoute() {
        // 获取目标位置
        Coord targetPos = moveTargetSupplier.get();
        if (targetPos == null) {
            return;
        }
        // 重置已访问节点map和开放列表
        visitedNodes.clear();
        openList.clear();
        // 将起点加入已访问节点map和开放列表，设置节点状态为开放
        Node startNode = new Node(coord.getX(), coord.getY(), null);
        visitedNodes.put(startNode, startNode.open());
        openList.offer(startNode);
        while (!openList.isEmpty()) {
            // 从开放列表中取出 F (G+H) 值最小的节点，设置节点状态为关闭
            Node node = openList.poll().close();
            // 如果当前节点在目标位置附近，则寻路完成，设置机器人方向
            double sqrDistance = Math.pow(node.x - targetPos.getX(), 2) + Math.pow(node.y - targetPos.getY(), 2);
            if (sqrDistance <= Math.pow(moveTargetDistance + GRID_SIZE * 1.4, 2)) {
                if (node == startNode) {
                    if (sqrDistance <= Math.pow(moveTargetDistance, 2)) {
                        nextNode = node;
                        // 不需要移动
                        nextSpeed = 0;
                    } else {
                        nextNode = null;
                        direction.set(targetPos.getX() - coord.getX(), targetPos.getY() - coord.getY());
                        nextSpeed = Game.ROBOT_MAX_SPEED; // TODO 不在这里设置速度，而是使用逻辑来设置
                    }
                    return;
                }
                while (node.parent != startNode) {
                    node = node.parent;
                }
                nextNode = node;
                direction.set(node.x - coord.getX(), node.y - coord.getY());
                nextSpeed = Game.ROBOT_MAX_SPEED; // TODO 不在这里设置速度，而是使用逻辑来设置
                return;
            }
            // 获取当前节点的相邻节点
            List<Node> neighbors = validNeighborNodesOf(node, targetPos);
            for (Node neighbor : neighbors) {
                if (node == startNode) {
                    // 设置转向代价
                    double turnX = (neighbor.x - node.x) / GRID_SIZE;
                    double turnY = (neighbor.y - node.y) / GRID_SIZE;
                    double turnCost = 3.5 * GRID_SIZE * (-(turnX * direction.getX() + turnY * direction.getY())
                            / ((turnX * turnX + turnY * turnY) + (direction.getX() * direction.getX() + direction.getY() * direction.getY())));
                    neighbor.g += turnCost;
                }
                Node visitedNode = visitedNodes.get(neighbor);
                if (visitedNode == null) {
                    // 若未访问过，则加入已访问节点map和开放列表，并设置节点状态为开放
                    visitedNodes.put(neighbor, neighbor.open());
                    openList.offer(neighbor);
                } else if (visitedNode.state && neighbor.g < visitedNode.g) {
                    // 若已在开放列表中且经由此节点具有更小的 G 值，则更新开放列表中的节点
                    visitedNode.g = neighbor.g;
                    visitedNode.parent = node;
                }
            }
        }
        // 开放列表为空，表示无法到达目标位置，停止移动
        log.debug("无法到达目标位置，寻路失败");
        nextSpeed = 0;
    }
    
    /**
     * 设置发生碰撞的机器人
     */
    public void setCollidedRobot(Robot robot) {
        collidedRobot = robot;
        unreachableValidDistance = GRID_SIZE + GRID_SIZE;
    }
    
    /**
     * 执行一个 Tick 的操作
     */
    public void tickAction() {
        // 执行逻辑
        logics.forEach(logic -> {
            try {
                logic.execute(this);
            } catch (Exception e) {
                log.debug("机器人逻辑执行出错");
            }
        });
        // 设置速度
        speed = nextSpeed;
        // 造成攻击伤害
        doAttack();
        if (moveTargetSupplier != null) {
            // 寻路
            findRoute();
            moveTargetSupplier = null;
        }
        // 移动
        tickMove();
        // 进入己方能量塔的能量范围时回血
        if (coord.sqrDistanceTo(player.team.towerCoord) < Game.SQR_TOWER_ENERGY_RADIUS) {
            blood += Game.TOWER_ENERGY_VALUE;
            if (blood > Game.ROBOT_MAX_BLOOD) {
                blood = Game.ROBOT_MAX_BLOOD;
            }
        }
    }
    
    @Override
    public int hashCode() {
        return System.identityHashCode(this);
    }
    
    @Override
    public boolean equals(Object o) {
        return this == o;
    }
    
    /**
     * 换一个方向
     */
    private void nextDirection() {
        if (direction.getX() >= 0 && direction.getY() < 0) {
            direction.set(1, 0);
        } else if (direction.getX() > 0) {
            direction.set(0, 1);
        } else if (direction.getY() > 0) {
            direction.set(-1, 0);
        } else {
            direction.set(0, -1);
        }
    }
    
    /**
     * 造成攻击伤害
     */
    private void doAttack() {
        if (attackDisabledTime == 0) {
            boolean attacked = false;
            if (attackTarget instanceof Robot) {
                Robot target = (Robot) attackTarget;
                Team rivalTeam = player.team.room.rivalOf(player.team);
                if ((target.blood -= attackValue) <= 0) {
                    rivalTeam.robots.remove(target);
                }
                attacked = true;
                attackOffset.set(target.coord.getX() - coord.getX(), target.coord.getY() - coord.getY());
            } else if (attackTarget instanceof Team) {
                Team target = (Team) attackTarget;
                if ((target.towerBlood -= attackValue) <= 0) {
                    // 获胜
                    player.team.room.winner.compareAndSet(null, player.team);
                }
                attacked = true;
                attackOffset.set(target.towerCoord.getX() - coord.getX(), target.towerCoord.getY() - coord.getY());
            }
            if (attacked) {
                attackTarget = null;
                attackDisabledTime = 33;
            }
        } else {
            if ((attackDisabledTime -= 1) <= 0) {
                attackDisabledTime = 0;
            }
            attackOffset.set(0.0, 0.0);
        }
    }
    
    /**
     * 获取合法的相邻节点 (寻路)
     *
     * @param node      节点
     * @param targetPos 目标位置
     * @return 相邻节点
     */
    private List<Node> validNeighborNodesOf(Node node, Coord targetPos) {
        List<Node> neighbors = new ArrayList<>(8);
        // 8个方向的坐标偏移
        double[] dx = {-GRID_SIZE, -GRID_SIZE, 0, GRID_SIZE, GRID_SIZE, GRID_SIZE, 0, -GRID_SIZE};
        double[] dy = {0, -GRID_SIZE, -GRID_SIZE, -GRID_SIZE, 0, GRID_SIZE, GRID_SIZE, GRID_SIZE};
        for (int i = 0; i < 8; i++) {
            double nx = node.x + dx[i];
            double ny = node.y + dy[i];
            // 边界检测
            if (nx < 0 || ny < 0 || nx > Game.ROBOT_MAX_POS_X || ny > Game.ROBOT_MAX_POS_Y) {
                continue;
            }
            // 能量塔碰撞检测
            Team rivalTeam = player.team.room.rivalOf(player.team);
            if (Game.collided(nx, ny, nx + Game.ROBOT_SIZE, ny + Game.ROBOT_SIZE,
                    player.team.towerCoord.getX(), player.team.towerCoord.getY(),
                    player.team.towerCoord.getX() + Game.TOWER_SIZE, player.team.towerCoord.getY() + Game.TOWER_SIZE)
                    || Game.collided(nx, ny, nx + Game.ROBOT_SIZE, ny + Game.ROBOT_SIZE,
                    rivalTeam.towerCoord.getX(), rivalTeam.towerCoord.getY(),
                    rivalTeam.towerCoord.getX() + Game.TOWER_SIZE, rivalTeam.towerCoord.getY() + Game.TOWER_SIZE)) {
                continue;
            }
            Node neighbor = new Node(nx, ny, node);
            // 与其他机器人的碰撞处理
            if (collidedRobot != null) {
                // 发生了碰撞则下一个节点不可达
                if (neighbor.equals(nextNode)) {
                    continue;
                }
                // 过滤与发生碰撞的机器人重叠的节点
                if (collidedRobot.collidedWith(nx, ny, nx + Game.ROBOT_SIZE, ny + Game.ROBOT_SIZE)) {
                    continue;
                }
            }
            neighbor.g = node.g + ((i & 1) == 0 ? 10 : 14);
            neighbor.h = (int) (Math.abs(targetPos.getX() - nx) + Math.abs(targetPos.getY() - ny));
            neighbors.add(neighbor);
        }
        return neighbors;
    }
    
    /**
     * 消耗不可达节点剩余有效路程
     *
     * @param distance 消耗的路程
     */
    private void consumeUnreachableValidDistance(double distance) {
        if (unreachableValidDistance != 0 && (unreachableValidDistance -= distance) <= 0) {
            // 重置发生碰撞的机器人
            collidedRobot = null;
            unreachableValidDistance = 0;
        }
    }
    
    /**
     * 移动一个 Tick，更新坐标
     */
    private void tickMove() {
        Team rivalTeam = player.team.room.rivalOf(player.team);
        double xSpeed = direction.xSpeedOf(speed);
        double ySpeed = direction.ySpeedOf(speed);
        double newX = coord.getX() + xSpeed;
        double newY = coord.getY() + ySpeed;
        if (xSpeed != 0) {
            // 边界检测
            if (newX > Game.ROBOT_MAX_POS_X) {
                newX = Game.ROBOT_MAX_POS_X;
            }
            if (newX < 0) {
                newX = 0;
            }
            // 能量塔碰撞检测
            if (player.team.towerCollidedWith(newX, coord.getY(), newX + Game.ROBOT_SIZE, coord.getY() + Game.ROBOT_SIZE)) {
                newX = xSpeed > 0 ? player.team.towerCoord.getX() - Game.ROBOT_SIZE : player.team.towerCoord.getX() + Game.TOWER_SIZE;
            } else if (rivalTeam.towerCollidedWith(newX, coord.getY(), newX + Game.ROBOT_SIZE, coord.getY() + Game.ROBOT_SIZE)) {
                newX = xSpeed > 0 ? rivalTeam.towerCoord.getX() - Game.ROBOT_SIZE : rivalTeam.towerCoord.getX() + Game.TOWER_SIZE;
            }
        }
        if (ySpeed != 0) {
            // 边界检测
            if (newY > Game.ROBOT_MAX_POS_Y) {
                newY = Game.ROBOT_MAX_POS_Y;
            }
            if (newY < 0) {
                newY = 0;
            }
            // 能量塔碰撞检测
            if (player.team.towerCollidedWith(coord.getX(), newY, coord.getX() + Game.ROBOT_SIZE, newY + Game.ROBOT_SIZE)) {
                newY = ySpeed > 0 ? player.team.towerCoord.getY() - Game.ROBOT_SIZE : player.team.towerCoord.getY() + Game.TOWER_SIZE;
            } else if (rivalTeam.towerCollidedWith(coord.getX(), newY, coord.getX() + Game.ROBOT_SIZE, newY + Game.ROBOT_SIZE)) {
                newY = ySpeed > 0 ? rivalTeam.towerCoord.getY() - Game.ROBOT_SIZE : rivalTeam.towerCoord.getY() + Game.TOWER_SIZE;
            }
        }
        coord.set(newX, newY);
        consumeUnreachableValidDistance(Math.abs(coord.offsetX()) + Math.abs(coord.offsetY()));
    }
}
